package com.bluexmicro.example.page.pick.peripheral

import android.bluetooth.le.ScanResult
import android.content.Context
import android.util.Log
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.bluexmicro.android.coroutineble.model.BleDevice
import com.bluexmicro.android.coroutineble.scanPeripheral
import com.bluexmicro.example.page.pick.peripheral.filter.ScanFilterElements
import com.bluexmicro.example.page.pick.peripheral.scan.PeripheralModel
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock


class PeripheralVMFactory(private val dataStore: DataStore<Preferences>) :
    ViewModelProvider.NewInstanceFactory() {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return PeripheralViewModel(dataStore) as T
    }

}

class PeripheralViewModel(
    private val dataStore: DataStore<Preferences>
) : ViewModel() {

    private val moshi = Moshi.Builder().build()
    private val jsonAdapter: JsonAdapter<ScanFilterElements> =
        moshi.adapter(ScanFilterElements::class.java)

    private val keyOfFilterElements = stringPreferencesKey("ScanFilterElements")

    // -127 ~ 0
    val displayRssi = mutableStateOf(-127f)
    val displayKey = mutableStateOf("")
    val displayConnectable = mutableStateOf(true)//这里不用到

    val displayFilter = mutableStateOf(false)

    fun loadFilterElements() {
        viewModelScope.launch(Dispatchers.IO) {
            val flow: Flow<ScanFilterElements?> = dataStore.data
                .map { preferences ->
                    val json: String? = preferences[keyOfFilterElements]
                    if (json == null || json.isNullOrBlank()) return@map null
                    try {
                        jsonAdapter.fromJson(json)
                    } catch (e: Exception) {
                        null
                    }
                }
            flow.collect { nullableElements ->
                nullableElements?.apply {
                    displayRssi.value = (rssiThreshold).toFloat()
                    displayKey.value = key
                    displayConnectable.value = connectable
                }
            }
        }
    }

    fun save() {
        viewModelScope.launch(Dispatchers.IO) {
            dataStore.edit { settings ->
                val elements =
                    ScanFilterElements(
                        rssiThreshold = displayRssi.value.toInt(),
                        key = displayKey.value,
                        connectable = displayConnectable.value
                    )
                val jsonAdapter: JsonAdapter<ScanFilterElements> =
                    moshi.adapter(ScanFilterElements::class.java)
                settings[keyOfFilterElements] = jsonAdapter.toJson(elements)
            }
        }
    }


    private fun BleDevice.isTarget(context: Context): Boolean {
        val flag = displayKey.value.let {
            if (it.isEmpty() || it.isBlank()) {
                true
            } else if (displayName(context).contains(it, true)) {
                true
            } else mac.contains(it, true)
        }

        val rssi = rssi >= displayRssi.value
//        val flag2 = if (displayConnectable.value) connectable else true
        return  rssi && flag
    }

    private fun PeripheralModel.isTarget(): Boolean {
        val flag = displayKey.value.let {
            if (it.isEmpty() || it.isBlank()) {
                true
            } else if (displayName.value.contains(it, true)) {
                true
            } else macAddress.contains(it, true)
        }

        val rssi = rssi.value >= displayRssi.value
        return rssi && flag
    }

    ///////////////////////////////////////////////////////////////////////////
    //
    ///////////////////////////////////////////////////////////////////////////

    private val deviceModelMap = mutableMapOf<String, PeripheralModel>()
    val deviceModels = mutableStateListOf<PeripheralModel>()
    private var lastUpdateTimeMillis = 0L
    private val mutex = Mutex()
    private val mapMutex = Mutex()

    private var scanJob: Job? = null
    fun startScan(context: Context) {
        scanJob?.takeIf { it.isActive }?.apply { cancel() }
        scanJob = viewModelScope.launch {
            context.scanPeripheral().catch {
            }.buffer().collect {
                appendDevices(context, it)
            }
        }
    }

    fun stopScan() {
        scanJob?.takeIf { it.isActive }?.apply { cancel() }
    }

    private fun findPeripheralModel(mac: String): PeripheralModel? {
        for (model in deviceModels) {
            if (model.macAddress == mac) return model
        }
        return null
    }

    private fun PeripheralModel.updatePeripheralModel(context: Context, bleDevice: BleDevice) {
        val showName = bleDevice.displayName(context)
        if (displayName.value != showName) displayName.value = showName
        rssi.value = bleDevice.rssi
        val level = when (bleDevice.rssi) {
            in -50 until 0 -> 0
            in -70 until -50 -> 1
            in -90 until -70 -> 2
            in -127 until -90 -> 3
            else -> 4
        }
        if (rssiLevel.value != level) rssiLevel.value = level
    }

    /**
     * 1. 新增的会自动过滤掉
     * 2. 旧的数据要手动清除掉
     */
    private fun appendDevices(context: Context, scanResults: List<ScanResult>) =
        viewModelScope.launch(Dispatchers.Default) {
            mapMutex.withLock {
                for (scanResult in scanResults) {
                    val bleDevice = BleDevice(scanResult)
                    var model = deviceModelMap[bleDevice.mac]
                    if (model != null) {
                        model.updatePeripheralModel(context, bleDevice)
                    } else {
                        //如果不符合过滤条件，则不新增
                        if (bleDevice.isTarget(context)) {
                            model = PeripheralModel(bleDevice.device)
                            model.updatePeripheralModel(context, bleDevice)
                            deviceModelMap[bleDevice.mac] = model
                        }
                    }
                }
            }
            applyData()
        }

    private fun applyData() = viewModelScope.launch(Dispatchers.IO) {
        mutex.withLock {
            val flag = System.currentTimeMillis() - lastUpdateTimeMillis > 1000
            //maybe miss devices
            if (flag) {
                val copyOfCache = deviceModelMap.toMap()//防止并发
                for (macAddress in copyOfCache.keys) {
                    if (findPeripheralModel(macAddress) == null) {
                        copyOfCache[macAddress]?.let {
                            deviceModels.add(it)
                        }
                    }
                }
                lastUpdateTimeMillis = System.currentTimeMillis()
            }
        }
    }

    fun sortDevices() = viewModelScope.launch(Dispatchers.Default) {
        mutex.withLock {
            val copyOfCache = deviceModelMap.toMap()//防止并发
            val pendingRemoveKeys = mutableListOf<String>()
            val pendingRemoveValues = mutableListOf<PeripheralModel>()
            for (mac in copyOfCache.keys) {
                val model = copyOfCache[mac]
                if (model != null && !model.isTarget()) {
                    pendingRemoveKeys.add(mac)
                    pendingRemoveValues.add(model)
                }
            }
            mapMutex.withLock {
                for (mac in pendingRemoveKeys) {
                    deviceModelMap.remove(mac)
                }
            }
            for (model in pendingRemoveValues) {
                deviceModels.remove(model)
            }
            val newList = deviceModels.sortedWith { o1, o2 ->
                val diff = o1.rssi.value - o2.rssi.value
                if (diff == 0) {
                    0
                } else if (diff > 0) {
                    -1
                } else {
                    1
                }
            }
            deviceModels.clear()
            deviceModels.addAll(newList)
        }
    }
}